package eval.utilities.random;

import cern.jet.random.AbstractDistribution;
import cern.jet.random.Normal;
import cern.jet.random.Uniform;
import eval.utilities.configuration.ExperimentConfiguration;

/**
 * Class implementing an alternating probabilistic distribution Values generated
 * by this class alternates between two periods: low and high.
 * 
 * The duration of each period is probabilistically fixed through a Poisson
 * random variable.
 * 
 * @author Nicolo' M. Calcavecchia <calcavecchia@elet.polimi.it>
 */
public class AlternatingDistribution extends AbstractDistribution {

	private ExperimentConfiguration conf = ExperimentConfiguration
			.getSingleton();

	private static final long serialVersionUID = 1L;

	private boolean isLowPeriod;
	private int periodDuration;
	private int periodCounter = 0;

	// distributions for time
	private AbstractDistribution lowPeriodDistribution;
	private AbstractDistribution highPeriodDistribution;

	// distributions for demand
	private AbstractDistribution lowPeriodDemandDistribution;
	private AbstractDistribution highPeriodDemandDistribution;
	private int lowPeriodAverageDemand;
	private int highPeriodAverageDemand;

	public AlternatingDistribution(int lowPeriodAverageDuration,
			int lowPeriodAverageDemand, int highPeriodAverageDuration,
			int highPeriodAverageDemand) {

		// low period data
		this.lowPeriodDistribution = RandomUtilities
				.getNewConstantDistribution(lowPeriodAverageDuration);
		this.lowPeriodDemandDistribution = RandomUtilities
				.getNewNormalDistribution(lowPeriodAverageDemand,lowPeriodAverageDemand * conf.demandDistributionSTD);
		
		this.lowPeriodAverageDemand = lowPeriodAverageDemand;

		// high period data
		this.highPeriodDistribution = RandomUtilities
				.getNewConstantDistribution(highPeriodAverageDuration);

		this.highPeriodDemandDistribution = RandomUtilities
				.getNewNormalDistribution(highPeriodAverageDemand,highPeriodAverageDemand *  conf.demandDistributionSTD);

		this.highPeriodAverageDemand = highPeriodAverageDemand;
		init(lowPeriodAverageDuration, highPeriodAverageDuration);
	}

	private void init(int lowPeriodAverageDuration,
			int highPeriodAverageDuration) {
		// start with low period
		isLowPeriod = true;
		periodDuration = lowPeriodDistribution.nextInt();

		Uniform initializingDistribution = RandomUtilities
				.getNewUniformDistribution(0, conf.experimentTicks);
//		Normal initializingDistribution = RandomUtilities.getNewNormalDistribution(0, conf.experimentTicks);
//				.getNewUniformDistribution(0, conf.experimentTicks);

		int initializationCycles = initializingDistribution.nextInt();
		for (int i = 0; i < initializationCycles; i++) {
			nextInt();
		}
	}

	@Override
	public double nextDouble() {
		periodCounter++;

		double demand = rangeValue(isLowPeriod ? lowPeriodDemandDistribution
				.nextInt() : highPeriodDemandDistribution.nextInt());

		if (periodCounter >= periodDuration) {
			isLowPeriod = !isLowPeriod;
			periodCounter = 0;
			periodDuration = (isLowPeriod) ? lowPeriodDistribution.nextInt()
					: highPeriodDistribution.nextInt();
		}

		return demand;
	}
	
	@Override
	public int nextInt() {
		periodCounter++;

		int demand = rangeValue(isLowPeriod ? lowPeriodDemandDistribution
				.nextInt() : highPeriodDemandDistribution.nextInt());

		if (periodCounter >= periodDuration) {
			isLowPeriod = !isLowPeriod;
			periodCounter = 0;
			periodDuration = (isLowPeriod) ? lowPeriodDistribution.nextInt()
					: highPeriodDistribution.nextInt();
		}

		return demand;
	}
	
	private int rangeValue(int value) {
		value = Math.min(value, highPeriodAverageDemand);
		value = Math.max(value, lowPeriodAverageDemand);
		return value;
	}
//	private double rangeValue(double value) {
//		value = Math.min(value, highPeriodAverageDemand);
//		value = Math.max(value, lowPeriodAverageDemand);
//		return value;
//	}

	public void setInitValue(int initializationValue) {
		this.periodCounter = initializationValue % periodDuration;
	}
}
